package cn.wizzer.app.web.modules.controllers.account;


import cn.wizzer.app.sys.modules.models.SysBankLib;
import cn.wizzer.app.sys.modules.models.SysMsg;
import cn.wizzer.app.sys.modules.models.SysPayWay;
import cn.wizzer.app.sys.modules.models.SysPayWayBank;
import cn.wizzer.app.sys.modules.services.*;
import cn.wizzer.app.user.modules.models.User;
import cn.wizzer.app.user.modules.models.UserExchange;
import cn.wizzer.app.user.modules.models.UserRecharge;
import cn.wizzer.app.user.modules.models.UserWithdraw;
import cn.wizzer.app.user.modules.models.enums.*;
import cn.wizzer.app.user.modules.services.UserService;
import cn.wizzer.app.user.modules.services.account.*;
import cn.wizzer.app.web.commons.filter.ApiTokenFilter;
import cn.wizzer.app.web.commons.ulog.annotation.ULog;
import cn.wizzer.framework.base.model.enums.BizExceptionEnum;
import cn.wizzer.framework.page.Pagination;
import cn.wizzer.framework.util.Result;
import cn.wizzer.framework.util.param.ParamVerify;
import cn.wizzer.notify.WkNotifyUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.util.ByteSource;
import org.nutz.dao.Chain;
import org.nutz.dao.Cnd;
import org.nutz.dao.Sqls;
import org.nutz.integration.jedis.RedisService;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Strings;
import org.nutz.lang.Times;
import org.nutz.log.Log;
import org.nutz.log.Logs;
import org.nutz.mvc.Mvcs;
import org.nutz.mvc.annotation.*;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Calendar;

@IocBean
@At("/api/account")
public class ApiAccountController {
    private final static Log log = Logs.get();

    @Inject
    @Reference
    private SysUserService sysUserService;

    @Inject
    private UserService userService;

    @Inject
    private RedisService redisService;

    @Inject
    @Reference(retries = 0)
    private UserRechargeService rechargeService;

    @Inject
    @Reference(retries = 0)
    private UserAccountService accountService;

    @Inject
    @Reference(retries = 0)
    private UserAccountRecordService recordService;

    @Inject
    @Reference
    private SysMsgService sysMsgService;

    @Inject
    @Reference
    private WkNotifyUtil wkNotifyUtil;

    @Reference
    @Inject
    private SysParamService sysParamService;

    @Reference
    @Inject
    private SysCurrencyService sysCurrencyService;

    @Inject
    @Reference(retries = 0)
    private UserWithdrawService userWithdrawService;

    @Inject
    @Reference(retries = 0)
    private UserExchangeService exchangeService;

    @Inject
    @Reference(retries = 0)
    private UserRebateService rebateService;

    @Inject
    @Reference
    private SysPayWayBankService payWayBankService;

    @Inject
    @Reference
    private SysPayWayService sysPayWayService;

    @Inject
    @Reference
    private SysBankLibService bankLibService;

    //region recharge
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户发起充值", msg = "申请充值金额: ${amount},币种：${currencyId}")
    public Object recharge(@ParamVerify(required = true) int amount,
                           @ParamVerify(required = true) PayChannel channel,
                           @ParamVerify(required = true) String currencyId,
                           String bankLibId) throws Exception {
        String appid = Mvcs.getReq().getHeader("appid");
        // 游客账号不能充值
        User user = userService.fetch(appid);
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.VISITOR_CANNOT_RECHARGE);
        }
        //region 验证渠道可用
        SysPayWay payWay = sysPayWayService.fetch(
                Cnd.where("currencyId", "=", currencyId)
                        .and("payWay", "=", channel)
        );
        if (null == payWay) {
            return Result.error(BizExceptionEnum.PAY_WAY_NOT_NULL);
        }
        //verify payWay
        if (payWay.isDisabled()) {
            log.error(BizExceptionEnum.PAY_WAY_DISABLED);
            return Result.error(BizExceptionEnum.PAY_WAY_DISABLED);
        }
        BigDecimal decimalAmount = new BigDecimal(amount);
        if (null != payWay.getMinRechargeAmount() && decimalAmount.compareTo(payWay.getMinRechargeAmount()) < 0) {
            log.error(BizExceptionEnum.LESS_THAN_MIN_AMOUNT);
            Result.breakWith(BizExceptionEnum.LESS_THAN_MIN_AMOUNT);
        }
        if (null != payWay.getMaxRechargeAmount() && decimalAmount.compareTo(payWay.getMaxRechargeAmount()) > 0) {
            log.error(BizExceptionEnum.MORE_THAN_MAX_AMOUNT);
            Result.breakWith(BizExceptionEnum.MORE_THAN_MAX_AMOUNT);
        }
        SysPayWayBank bank = null;
        //必选银行卡渠道判断
        if (channel.equals(PayChannel.BANK)) {
            if (Strings.isEmpty(bankLibId)) {
                log.error("the param named 'bankLibId' can not been null");
                return Result.error().addCode(-3).addDetail("the param named 'bankLibId' can not been null");
            }
            SysBankLib bankLib = bankLibService.fetch(bankLibId);
            if (!bankLib.getCurrencyId().equals(currencyId)) {
                log.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
                return Result.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
            }
            //支付方式禁用
            bank = payWayBankService.fetch(
                    Cnd.where("bankLibId", "=", bankLibId)
                            .and("payWayId", "=",
                                    payWay.getId()
                            )
            );
            //指定支付方式下无对应银行
            if (bank == null) {
                log.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
                return Result.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
            } else {
                //银行支付渠道关联表禁用
                if (!bank.isDisabled()) {
                } else {
                    log.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
                    return Result.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
                }
                //银行禁用
                if (bankLib.isDisabled()) {
                    log.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
                    return Result.error(BizExceptionEnum.ENSURE_BEHAVIORAL_SAFETY);
                }
            }
        }
        //endregion
        if (PayChannel.ONE_WALLET.equals(channel)) {
            String url = rechargeService.oneWalletToPay(appid, currencyId, amount, bank);
            return Result.success().addData(url);
        } else if (PayChannel.XINDO_VND.equals(channel)) {
            String requestURL = Mvcs.getReq().getRequestURL().toString();

            String urls = rechargeService.xindovndToPay(appid, currencyId, amount, requestURL);
            return Result.success().addData(urls);
        } else if (PayChannel.ELEPHANT168.equals(channel)) {
            String requestURL = Mvcs.getReq().getRequestURL().toString();
            String urls = rechargeService.elephantToPay(appid, currencyId, amount, requestURL);
            return Result.success().addData(urls);
        } else if (channel.toString().contains("GLOBALPAY")) {
            String requestURL = Mvcs.getReq().getRequestURL().toString();
            String urls = rechargeService.globalPayToPay(appid, currencyId, channel, amount, requestURL);
            return Result.success().addData(urls);
        }
        UserRecharge recharge = rechargeService.add(amount, appid, channel, currencyId, bankLibId);
        return Result.success().addData(recharge.getId());
    }

    @At("/recharge/sure")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户确认转账", msg = "确认转账: ${rechargeId},付款(人/地址): ${payer}")
    public Object sure(@ParamVerify(name = "rechargeId", required = true) String rechargeId,
                       String payer) {
        String appid = Mvcs.getReq().getHeader("appid");
        UserRecharge recharge = rechargeService.fetch(rechargeId);
        //或确认为手动充值通道方可修改
        int i = recharge != null ?
                recharge.getUserId().equals(appid) ?
                        recharge.getStatus().equals(RechargeStatusEnum.PAYING) ?
                                rechargeService.update(Chain.make("status", RechargeStatusEnum.PAID)
                                                .add("payer", payer)
                                                .add("paymentAt", Times.getTS()),
                                        Cnd.where("id", "=", rechargeId)) : 0 : 0 : 0;
        i = 1 / i;
        User user = userService.fetch(appid);
        //构建消息通知
        SysMsg msg = sysMsgService.createMsg("system", "充值提醒",
                String.format("<p>消息内容:单号[%s]用户[%s]邮箱[%s]于<span style='color:red'>%s</span> 充值[%s]，" +
                                "附加留言[%s]，真实姓名[%s]，请核对确认，请到用户>账户管理->充值管理里对相应记录进行确认通过或拒绝。" +
                                "<a href=%s style='color:blue'>点击处理</a></p>",
                        recharge.getNo(), user.getUserName(), user.getEmail(),
                        DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"),
                        recharge.getAmount(), recharge.getSign(), recharge.getPayer(),
                        "/platform/user/account/recharge?no=" + recharge.getNo()),
                null);
        //插入通知
        sysMsgService.saveMsg(msg, null);
        //后台通知管理员
        wkNotifyUtil.notify(msg, null);
        return Result.success();
    }

    @At("/recharge/cancel")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户取消充值记录", msg = "取消记录: ${rechargeId}")
    public Object cancel(@ParamVerify(name = "rechargeId", required = true) String rechargeId) {
        String appid = Mvcs.getReq().getHeader("appid");
        UserRecharge recharge = rechargeService.fetch(rechargeId);
        //或确认为手动充值通道方可修改
        int i = recharge != null ?
                recharge.getUserId().equals(appid) ?
                        //待转账或已转账状态下允许取消
                        (recharge.getStatus().equals(RechargeStatusEnum.PAID) || recharge.getStatus().equals(RechargeStatusEnum.PAYING)) ?
                                rechargeService.update(Chain.make("status", RechargeStatusEnum.CANCELLED)
                                        .add("cancelAt", Times.getTS()), Cnd.where("id", "=", rechargeId)) : 0 : 0 : 0;
        i = 1 / i;
        return Result.success();
    }

    @At("/recharge/record")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object rechargeRecord(
            @Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize,
            Integer status, long startTime, long endTime) {
        String appid = Mvcs.getReq().getHeader("appid");
        Cnd cnd = Cnd.where("userId", "=", appid);
        if (status != null) {
            cnd.and("status", "=", status);
        }
        if (startTime != 0) {
            cnd.and("opAt", ">=", startTime);
        }
        if (endTime != 0) {
            cnd.and("opAt", "<=", endTime);
        }
        cnd.orderBy("opAt", "desc");
        return Result.success().addData(rechargeService.listPage(pageNumber, pageSize, cnd));
    }

    @At("/recharge/detail")
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object rechargeDetail(String id) {
        String appid = Mvcs.getReq().getHeader("appid");
        String sql = Sqls.create(" where userId = @appid and r.id=@rid").setParams(Result.createMap("appid", appid, "rid", id)).toString();
        return Result.success().addData(rechargeService.detail(sql));
    }
    //endregion

    //region account
    @At("/record")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object record(
            @Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize,
            BizTypeEnum bizType, long startTime, long endTime, String currencyId) {
        String appid = Mvcs.getReq().getHeader("appid");
        try {
            Cnd cnd = Cnd.where("userId", "=", appid);
            cnd.orderBy("ctAt", "desc");
            if (bizType != null) {
                cnd.and("bizType", "=", bizType);
            }
            if (startTime != 0) {
                cnd.and("ctAt", ">=", startTime);
            }
            if (endTime != 0) {
                cnd.and("ctAt", "<=", endTime);
            }
            if (Strings.isNotBlank(currencyId)) {
                cnd.and("currencyId", "=", currencyId);
            }
            Pagination pagination = recordService.listPage(pageNumber, pageSize, cnd);
            return Result.success().addData(pagination);
        } catch (Exception e) {
            log.error(e.getMessage());
            return Result.error();
        }
    }


    @At("/enums")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object recordEnums() {
        return Result.success(EnumUtils.getEnumList(BizTypeEnum.class));
    }

    @At("/remain")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object remain(@Param(value = "currencyId", df = "default") String currencyId) {
        try {
            String appid = Mvcs.getReq().getHeader("appid");

            return Result.success().addData(accountService.select(appid, currencyId));
        } catch (Exception e) {
            log.error(e.getMessage());
            return Result.error();
        }
    }

    @At("/wallets")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object wallets() {
        String appid = Mvcs.getReq().getHeader("appid");
        return Result.success(accountService.wallets(appid));
    }


    @At("/totalBalance")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object totalBalance() {
        String appid = Mvcs.getReq().getHeader("appid");
        return Result.success(accountService.calTotalBalance(appid));
    }

    @At("/wallet/record")
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object walletRecord(String currencyId, String toCurrencyId, int pageNumber, int pageSize,
                               Long startTime, Long endTime) {
        return Result.success(recordService.queryWithdrawAndRecharge(Mvcs.getReq().getHeader("appid"), currencyId, pageNumber, pageSize));
    }

    @At
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object getBizType() {
        return Result.success().addData(BizTypeEnum.toList());
    }

    //endregion

    //region withdraw

    /**
     * @param channel
     * @param amount
     * @param cardId
     * @param code       交易密码(做交易密码功能之前的参数名，方便前端就不改了)
     * @param currencyId
     * @return
     * @throws Exception
     */
    @At
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户提现申请", msg = "申请提现金额: ${amount},币种：${currencyId}")
    public Object cash(WithdrawChannel channel,
                       @ParamVerify(required = true, name = "amount") double amount,
                       @ParamVerify(required = true, name = "cardId") String cardId,
                       @ParamVerify(required = true) String code,
                       String currencyId) throws Exception {
        String appid = Mvcs.getReq().getHeader("appid");
        User user = userService.fetch(appid);
        // 游客账号不能提现
        if (user.isVisitor()) {
            return Result.error(BizExceptionEnum.VISITOR_CANNOT_WITHDRAW);
        }
        // 校验交易密码
        String traderPwd = new Sha256Hash(code, ByteSource.Util.bytes(user.getSalt()), 1024).toHex();
        if (!traderPwd.equals(user.getTransactionPwd())) {
            return Result.error(BizExceptionEnum.PWD_VERIFICATION_ERROR);
        }
        // 提现去掉小数
        BigDecimal decimalAmount = new BigDecimal(amount).setScale(0, RoundingMode.DOWN);
        UserWithdraw withdraw = userWithdrawService.add(channel, currencyId, decimalAmount, appid, cardId);
        //构建消息通知
        SysMsg msg = sysMsgService.createMsg("system", "WITHDRAW",
                String.format("<p>NO[%s]USER[%s]EMAIL[%s]AT<span style='color:red'>%s</span> WITHDRAW[%s]," +
                                "<a href=%s style='color:blue'>CLICK HERE</a></p>",
                        withdraw.getNo(), user.getUserName(), user.getEmail(),
                        DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss"),
                        withdraw.getAmount(), "/platform/user/account/withdraw?no=" + withdraw.getNo()),
                null);
        //插入通知
        sysMsgService.saveMsg(msg, null);
        //后台通知管理员
        wkNotifyUtil.notify(msg, null);

        return Result.success().addData(withdraw.getId());
    }

    @At
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object cashFreeTimes() {
        String appid = Mvcs.getReq().getHeader("appid");
        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.DATE, 1);
        int currentMouthTimes = userWithdrawService.count(Cnd.where("userId", "=", appid)
                .and("status", "in", Result.createList(WithdrawStatusEnum.APPLYING, WithdrawStatusEnum.PENDING_BANK, WithdrawStatusEnum.SUCCESS))
                .and("ctAt", ">=", cal.getTime().getTime() / 1000)
        );
        return Result.success(Result.createMap(
                "currentMouthWithDrawTimes", currentMouthTimes,
                "mouthFreeTimes", userService.fetch(appid).getWithdrawFreeTimes(),
                "withdrawFee", sysParamService.fetch().getWithdrawFee()
        ));
    }

    @At("/cash/cancel")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户取消提现记录", msg = "取消记录: ${rechargeId}")
    public Object cashCancel(@ParamVerify(required = true, name = "cashId") String cashId) {
        String appid = Mvcs.getReq().getHeader("appid");
        UserWithdraw withdraw = userWithdrawService.fetch(cashId);
        //或确认为手动充值通道方可修改
        int i = withdraw != null ?
                withdraw.getUserId().equals(appid) ?
                        withdraw.getStatus().equals(WithdrawStatusEnum.APPLYING) ?
                                userWithdrawService.update(Chain.make("status", RechargeStatusEnum.CANCELLED), Cnd.where("id", "=", cashId)) : 0 : 0 : 0;
        i = 1 / i;
        return Result.success();
    }

    @At("/cash/record")
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object cashRecord(@Param("pageNumber") int pageNumber, @Param("pageSize") int pageSize,
                             Integer status, long startTime, long endTime) {
        String appid = Mvcs.getReq().getHeader("appid");
        Cnd cnd = Cnd.where("userId", "=", appid);
        if (status != null) {
            cnd.and("status", "=", status);
        }
        if (startTime != 0) {
            cnd.and("opAt", ">=", startTime);
        }
        if (endTime != 0) {
            cnd.and("opAt", "<=", endTime);
        }
        cnd.orderBy("opAt", "desc");
        return Result.success().addData(userWithdrawService.listPage(pageNumber, pageSize, cnd));
    }

    @At("/cash/detail")
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object cashDetail(@Param("id") String id) {
        return Result.success().addData(userWithdrawService.detail(id));
    }

    //endregion

    //region exchange

    @At
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    @ULog(tag = "用户钱包兑换记录", msg = "发起兑换: ${amount},币种：${fromCurrencyId}《》${toCurrencyId}")
    public Object exchange(@ParamVerify(required = true) String fromCurrencyId,
                           @ParamVerify(required = true) String toCurrencyId,
                           @ParamVerify(required = true) String amount) {
        if (fromCurrencyId.equals(toCurrencyId)) {
            return Result.error();
        }
        String appid = Mvcs.getReq().getHeader("appid");
        UserExchange exchange = exchangeService.exchange(new BigDecimal(amount), fromCurrencyId, toCurrencyId, appid, "API");
        return Result.success().addData(exchange.getId());
    }

    @At("/exchange/detail")
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object exchangeDetail(@ParamVerify(required = true) String id) {
        return Result.success(exchangeService.fetchVO(id, Mvcs.getReq().getHeader("appid")));
    }

    @At("/exchange/record")
    @Ok("json:full")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Object exchangeRecord(String fromCurrencyId, String toCurrencyId, int pageNumber, int pageSize,
                                 Long startTime, Long endTime) {
        return Result.success(exchangeService.queryVO("", "", Mvcs.getReq().getHeader("appid"), fromCurrencyId, toCurrencyId, pageNumber, pageSize, startTime, endTime));
    }

    //endregion

    //region 详情
    @At
    @Ok("json")
    @Filters({@By(type = ApiTokenFilter.class)})
    public Result rebateDetail(String id) {
        return Result.success().addData(rebateService.detail(id, Mvcs.getReq().getHeader("appid")));
    }
    //endregion
}
